#include <stdio.h>
#include <stdint.h>

#if 1
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#endif

typedef union {
    struct {
        uint8_t b, g, r, a;
    };
    uint32_t argb;
} pix2_t;

typedef struct {
    uint8_t r, g, b;
} pix_t;


static inline int draw_end(char *buf)
{
    return sprintf(buf, "\e[0m\n");
}

static inline int draw_pix(char *buf, pix_t *pix)
{
    return sprintf(buf, "\e[48;2;%d;%d;%dm ", pix->r, pix->g, pix->b);
}

static inline int draw_line(char *buf, uint8_t *line, int w)
{
    pix_t *pix;
    char *start = buf;
    int n = 0;

    for (int i = 0; i < w; i++) {
        pix = (pix_t *) line;
        n = draw_pix(buf, pix);
        line += 3; // r, g, b -- 3bytes
        buf += n;
    }
    n = draw_end(buf);
    buf += n;

    return (buf - start);
}

static inline int draw_dualpix(char *buf, pix_t *pix_up, pix_t *pix_down)
{
    return sprintf(buf, "\e[48;2;%d;%d;%dm\e[38;2;%d;%d;%dm\u2580",
            pix_down->r, pix_down->g, pix_down->b, pix_up->r, pix_up->g, pix_up->b);
}

// attention: each call will read two line from mat
static inline int draw_dualline(char *buf, uint8_t *dline, int w)
{
    pix_t *pix_up, *pix_down;
    char *start = buf;
    int n = 0;

    for (int i = 0; i < w; i++) {
        pix_up   = (pix_t *) dline;
        pix_down = (pix_t *)(dline + w*3);
        n = draw_dualpix(buf, pix_up, pix_down);
        dline += 3; // r, g, b -- 3bytes
        buf += n;
    }
    n = draw_end(buf);
    buf += n;

    return (buf - start);
}

void draw_frame(int type, char *buf, uint8_t *mat, int w, int h)
{
    int n = 0;

    switch (type) {
        case 1:
            for (int i = 0; i < h; i++) {
                n = draw_line(buf, mat, w);
                mat += w * 3;
                buf += n;
            }
            break;
        case 2:
            for (int i = 0; i < h; i+=2) {
                n = draw_dualline(buf, mat, w);
                mat += w * 6;
                buf += n;
            }
            break;
        default:
            break;
    }
    // printf("r: %d\n", pix->r);
    // printf("g: %d\n", pix->g);
    // printf("b: %d\n", pix->b);
}

int64_t get_size(char *file)
{
    struct stat st;

    if (stat(file, &st) == 0)
        return st.st_size;

    return -1;
}

int show_rgb_image(char *image_rgb, int w, int h)
{
    int size = get_size(image_rgb);

#if 1
    char *mat = malloc(size);
    if (mat == NULL) {
        printf("malloc failed!\n");
        return -1;
    }
    memset(mat, 0, size);
#endif
    char *buf = malloc(size * 40);
    if (buf == NULL) {
        printf("malloc failed!\n");
        return -1;
    }
    memset(buf, 0, size * 40);

    // rows=60, cols=238
    int fd = open(image_rgb, O_RDONLY);
    if (fd < 0) {
        printf("open failed: `%s`\n", image_rgb);
        return -1;
    }

    if (read(fd, mat, size) != size) {
        printf("read failed!\n");
        return -1;
    }
    close(fd);

    draw_frame(2, buf, mat, w, h);

    printf("\ec");
    printf(buf);

    return 0;
}

int main(int argc, char *argv[])
{
    if (argc != 4) {
        printf("showrgb [image.rgb] [w] [h]\n");
        return -1;
    }

    int w = atoi(argv[2]);
    int h = atoi(argv[3]);

    return show_rgb_image(argv[1], w, h);
}
